unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, GLScene,
  GLViewer, ExtCtrls, StdCtrls, GLCadencer, GLGeomObjects, VectorGeometry, GLColor, GLObjects,
  lNetComponents, lNet, GLLCLViewer, Buttons, PairSplitter, NetworkCommon;

type

  { TForm1 }

  TForm1 = class(TForm)
    btnSend: TButton;
    Edit1: TEdit;
    GLCadencer1: TGLCadencer;
    GLCamera1: TGLCamera;
    GLDisk1: TGLDisk;
    GLDummyCube1: TGLDummyCube;
    GLLightSource1: TGLLightSource;
    GLLightSource2: TGLLightSource;
    GLLightSource3: TGLLightSource;
    GLScene1: TGLScene;
    Connect: TButton;
    SCN: TGLSceneViewer;
    GLSphere1: TGLSphere;
    GLSphere2: TGLSphere;
    GLSphere3: TGLSphere;
    GLSphere4: TGLSphere;
    Net: TLTCPComponent;
    Memo1: TMemo;
    Panel3: TPanel;
    Peek: TButton;
    Reset: TButton;
    Panel1: TPanel;
    Panel2: TPanel;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure GLCadencer1Progress(Sender: TObject; const deltaTime,
      newTime: Double);
    procedure LTCPComponent1Receive(aSocket: TLSocket);
    procedure NetReceive ( aSocket: TLSocket ) ;
    procedure PeekClick(Sender: TObject);
    procedure PeekResponse(Sender: TObject);
    procedure ResetClick(Sender: TObject);
    procedure ResetResponse(Sender: TObject);
    procedure SCNMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure SCNMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    function MouseWorldPos(x, y : Integer) : TVector;
    procedure OwnObject( AObject : TGLCustomSceneObject);
    procedure UpdateObject( AObject : TGLCustomSceneObject);
    procedure DropObject;
    procedure Reconstruct(MessageType: TMessageType; S : String);
  private
    { private declarations }
  public
    { public declarations }
    peeked : boolean;
    peeking : boolean;
    restarted : boolean;
    connected : boolean;
    StartTime : double;
    lastMouseWorldPos : TVector;
    Pick : TGLCustomSceneObject;
    oldpick : TGLCustomSceneObject;
    CurrentPick : TGLCustomSceneObject;
    mine : boolean;
  end; 

var
  Form1: TForm1; 

implementation

{ TForm1 }

// =======================================================================
// GLScene stuff
//

// -----------------------------------------------------------------------
// Assigning the 2d position into the 3d world
//
function TForm1.MouseWorldPos(x, y : Integer) : TVector;
var
   v : TVector;
begin
   y:=Scn.Height-y;
   if Assigned(currentPick) then begin
      SetVector(v, x, y, 0);
      Scn.Buffer.ScreenVectorIntersectWithPlaneXY(v, 0, Result);
   end else SetVector(Result, NullVector);
end;


// -----------------------------------------------------------------------
// Moving the mouse... (while clicking)
//
procedure TForm1.SCNMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer
  );
  
function min(a,b:double):double;
begin
  if a > b then result := b else result := a;
end;
function max(a,b:double):double;
begin
  if a > b then result := a else result := b;
end;

var newPos : TVector;
    t : TSphereInfo;
begin

  // You can't drag stuff around once you've peeked
  if peeked then exit;

  if not connected then exit;
  
  // You can't drag what you don't have
  if not assigned(CurrentPick) then exit;

  // Did you get a sphere?
  if (CurrentPick is TGLSphere) then  begin
    Mine := CurrentPick.Tag = 1;

  // Are you holding the mouse button down? and are you allowed to move it?
  if (ssLeft in Shift) and (Mine) then begin
    newPos:=MouseWorldPos(x, y);

    // Move the object if it's position changed
    if (VectorNorm(lastMouseWorldPos)<>0) then  begin
      CurrentPick.Position.Translate(VectorSubtract(newPos, lastMouseWorldPos));
      CurrentPick.Position.X := max(min(CurrentPick.Position.X, 2.5),-2.5);
      CurrentPick.Position.Y := max(min(CurrentPick.Position.Y, 2.5),-2.5);;
      

      // Send object to server
      t.mt := mtSphereInfo;
      t.Name := CurrentPick.Name;
      t.position := CurrentPick.Position;
      Net.Send(t,sizeof(t));

    end;
    lastMouseWorldPos:=newPos;
    end;
  end;
end;


// ------------------------------------------------------------------------
// Clicking the mouse
//
procedure TForm1.SCNMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var t : TOwnMessage;

begin

  if peeked then exit;
  if not connected then exit;

  Pick := nil;
  CurrentPick := Nil;
  Mine := false;

  pick:=SCN.Buffer.GetPickedObject(x, y) as TGLCustomSceneObject;
  
  if not Assigned(Pick) then exit;

  if not (Pick is TGLsphere) then exit;

  if pick.name = 'GLSphere4' then pick := pick.Parent as TGLcustomsceneobject;

  if pick is TGLSphere then begin

    // This should be set when received as acknowledged by server
    // CurrentPick := Pick;

    if not Peeking then begin
      // Request to "own" the picked object from server
      t.mt := mtOwn;
      t.Name := CurrentPick.Name;
      Net.Send(t,sizeof(t)) ;
    end
    else begin
      // Send the name of the object picked
      t.mt := mtPick;
      t.Name := CurrentPick.Name;
      Net.Send(t,sizeof(t)) ;
    end;

    //memo1.lines.insert(0,'CurrentPick Assigned');
  end;

  
  if Assigned(currentPick) then
    lastMouseWorldPos:=MouseWorldPos(x, y);
end;






procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
var t : double;
begin

  if StartTime = 0 then
    StartTime := Now();
  t := Now()-StartTime;

  // Move the lights around a bit...
  GLLightSource1.position.X := sin(t*24*60*60);
  GLLightSource1.position.Y := sin(t*24*60*60*1.002);
  GLLightSource2.position.X := sin(t*24*60*60*0.91);
  GLLightSource2.position.Y := sin(t*24*60*60*1.002*0.99);
  GLLightSource3.position.X := sin(t*24*60*60*1.2)*2;
  GLLightSource3.position.Y := sin(t*24*60*60*1.11)*2;
  
  // Should this all be done serverside?  So that the application simply updates it?
  // -> No... Server will just set the Peeked variable.
  
  if (not Peeked) then exit;

  GLSphere1.Position.z := 0+abs(sin(t*24*60*60*2))*2;
  GLSphere1.turnangle  := -90 + sin(t*24*60*60*2)*180;
  
  if not assigned(CurrentPick) then exit;

  if CurrentPick <> GLSphere1 then with (CurrentPick as TGLSphere) do begin
    Position.z := 0+abs(sin(t*24*60*60*2))*2;
    turnangle  := -90 + sin(-t*24*60*60*2)*180;
  end;
  
end;




procedure TForm1.Reconstruct(MessageType: TMessageType; S : String);

var a : TSphereInfo;
    b : TBoolMessage;
    c : TOwnMessage;

    i : integer;

begin
  // TODO : Reconstruct
  case MessageType of
    mtSphereInfo : begin
      a.mt := MessageType;
      S := copy(S,Sizeof(a.mt)+1,TSphereInfoSize);  // Do I need the exact size?!
      a.Name := copy(S,1,Sizeof(Str50));
      S := copy(S,Sizeof(Str50)+1,TSphereInfoSize);
      move(S,a.Position,Sizeof(a.Position));


      // Update the appropriate object's position
      for i := 0 to GLDummyCube1.count-1 do begin
        if GLDummyCube1.Children[i].Name = a.Name then
          GLDummyCube1.Children[i].Position := a.Position;
      end;

    end;
    mtBool       : begin
      b.mt := MessageType;
      S := copy(S,Sizeof(b.mt)+1,TboolmessageSize);  // Do I need the exact size?!
      move(S,b.Variable,sizeof(b.Variable));
      S := copy(S,Sizeof(b.Variable)+1,TBoolMessageSize);
      move(s,b.Value,Sizeof(b.Value));

      case b.Variable of
        btPeek    : peeked := b.value;
        btPeeking : peeking := b.value;
        btReset   : ResetResponse(nil);
        btDrop    : DropObject;
      end;
    end;
    mtOwn        : begin
      c.mt := MessageType;
      S := copy(S,Sizeof(c.mt)+1,TSphereInfoSize);  // Do I need the exact size?!
      c.Name := copy(S,1,Sizeof(Str50));


      // Own the named object
      for i := 0 to GLDummyCube1.count-1 do begin
        if GLDummyCube1.Children[i].Name = a.Name then
          OwnObject(GLDummyCube1.Children[i] as TGLCustomSceneObject);
      end;
    end;
  end;

end;





procedure TForm1.LTCPComponent1Receive(aSocket: TLSocket);
var s : string; 

function Reassemble(const s: string): Integer; // how many bytes were processed
var
 tmp: string;
 MsgSize: Integer; // the "message size" 4 bytes int
 //ObjectType: Byte; // this is used to hold the "type" byte
 Mt : TMessageType;
begin
 Result := 0;                        // assume we fail first

 tmp := s;

 move(tmp,mt,Sizeof(mt));

 case mt of
  mtSphereInfo : if length(tmp) < TSphereInfoSize  then exit;
  mtBool       : if length(tmp) < TBoolMessageSize then exit;
  mtOwn        : if length(tmp) < TOwnMessageSize  then exit;
 end;

 Reconstruct(mt,tmp);

 Result := Length(tmp);          // we processed the raw data + our size and type header
end;

begin
  // I don't know how, if S isn't the "whole" record, I don't lose the first part of S... 
   
  if aSocket.GetMessage(S) > 0 then
    Reassemble(S);
    
  (*// Depending on message do different stuff
  // Case Message of :

  // On Object Update Received
  UpdateObject(ReceivedObject);
  
  // On Granted permission to Own the selection
  OwnObject(ReceivedObject);
  
  // On drop message, drop the object
  DropObject;

  // Reset Received
  ResetResponse(nil);

  // Peek Received
  PeekResponse(nil);
  *)
end;

procedure TForm1.NetReceive ( aSocket: TLSocket ) ;
begin

end;






procedure TForm1.OwnObject( AObject : TGLCustomSceneObject);
begin
  CurrentPick := AObject;
  CurrentPick.Tag := 1;
end;

procedure TForm1.UpdateObject( AObject : TGLCustomSceneObject);
var i : integer;
begin

  for i := 0 to GLScene1.Objects.count -1 do
  if GLScene1.Objects[i].Name = AObject.Name then begin
    TGLSphere(GLScene1.Objects[i]) := TGLSphere(AObject);
    exit;
  end;
end;

procedure TForm1.DropObject;
begin
  CurrentPick.Tag := 0;
  CurrentPick := nil;
  Mine := false;
end;




procedure TForm1.PeekClick(Sender: TObject);
var t : TBoolMessage;
begin
  // TODO : Request to "Peek" from the server
  // PeekResponse(self);
  //t := TBoolMessage.Create(btPeek,TRUE);
  if not connected then exit;
  t.mt := mtBool;
  t.Variable := btPeek;
  t.Value := TRUE;
  net.Send(t,sizeof(t)) ;
end;

procedure TForm1.PeekResponse(Sender: TObject);
begin

end;

procedure TForm1.ResetClick(Sender: TObject);
var t : TBoolMessage;
begin
  // TODO : Request to "Peek" from the server
  // ResetResponse(self);
  // t := TBoolMessage.Create(btReset,TRUE);
  if not connected then exit;
  t.mt := mtBool;
  t.Variable := btReset;
  t.Value := TRUE;
  net.Send(t,Sizeof(t));
  //t.free;

end;



procedure TForm1.ResetResponse(Sender: TObject);
begin
  peeked := false;
  peeking := false;
  StartTime := 0;
  
  GlSphere1.position.x := 0;
  GlSphere1.position.y := 0;
  GlSphere1.position.z := 0;
  GlSphere1.turnangle  := -90;
  GlSphere1.Tag        := 0;
  GlSphere2.position.x := 1.75;
  GlSphere2.position.y := 0;
  GlSphere2.position.Z := 0;
  GlSphere2.turnangle  := -90;
  GlSphere2.Tag        := 0;
  GlSphere3.position.x := -1.75;
  GlSphere3.position.y := 0;
  GlSphere3.position.Z := 0;
  GlSphere3.turnangle  := -90;
  GlSphere3.Tag        := 0;

end;




procedure TForm1.FormCreate(Sender: TObject);
var a : TSphereInfo;
    b : TBoolMessage;
    c : TOwnMessage;
begin
  peeked := false;
  peeking := false;
  restarted := false;
  connected := false;
  StartTime := 0;


  //These variables are defined in NetworkCommon.pas
  TSphereInfoSize  := Sizeof(a);
  TBoolMessageSize := Sizeof(b);
  TOwnMessageSize  := Sizeof(c);
end;



initialization
  {$I main.lrs}

end.


